package com.pinapel.aixml.models.xml;

import java.io.File;
import java.io.InputStream;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import com.pinapel.aixml.models.xml.dom.sch.SvrlDomModel;
import com.pinapel.aixml.models.xml.errorHandlers.SchematronErrorReporter;

/**
 * @author Juanfran
 * @since May 17, 2012
 */
public class SchematronValidator {
	private static final String DIR_PATH = "resources" + System.getProperty("file.separator") + "schematron" + System.getProperty("file.separator");
	/** Filename of the Schematron XSLT file **/
	private static final String STEP_ONE_FILENAME = DIR_PATH + "iso_dsdl_include.xsl";
	private static final String STEP_TWO_FILENAME = DIR_PATH + "iso_abstract_expand.xsl";
	private static final String STEP_THREE_FILENAME = DIR_PATH + "iso_svrl_for_xslt1.xsl";
	
	
	/** Transformer factory for obtaining Transformer objects. We will reuse this **/
	private static TransformerFactory mTransformerFactory;
	/** Transformer for transforming the SCH into a XSLT. Once created it should not change and can be reused **/
	private static Transformer mSchematronTransformer;
	/** Contains the result of transforming the SCH document with the Schematron XSLT **/
	private Result mTransformResult;
	/** Transformer for applying the XSLT saved in mTransformResult to the XML document **/
	private Transformer mXMLTransformer;
	
	/** Boolean that indicates if the validator needs to be rebuilt again due to changes in the SCH document **/
	private boolean mUpdate;
	
	private SchematronErrorReporter mReporter;
	

	/**
	 * Builds a new SchematronValidator.
	 */
	public SchematronValidator() {
		mTransformerFactory = TransformerFactory.newInstance();		
		mUpdate = true;
	}
	
	/**
	 * Sets the SCH to validate with.
	 * @param inputStream
	 * @throws TransformerConfigurationException
	 * @throws TransformerException
	 */
	public void setSch (InputStream inputStream) throws TransformerConfigurationException, TransformerException {
//		System.out.println("Antes de generar transformer");

		// Set the error reporter for the TransformerFactory
		if(mTransformerFactory.getErrorListener() == null) {
			mTransformerFactory.setErrorListener(mReporter);
		}
		
		StreamSource stepOneXSLT;
		Transformer stepOne;
		StreamSource stepTwoXSLT;
		Transformer stepTwo;
		DOMResult stepTwoResult;;
		
		// Step One: Preprocess the SCH to include the various parts (if the SCH is divided in parts)
		stepOneXSLT = new StreamSource(new File(STEP_ONE_FILENAME));
		stepOne = mTransformerFactory.newTransformer(stepOneXSLT);
		stepOne.setErrorListener(mReporter);
		DOMResult stepOneResult = new DOMResult();
		stepOne.transform(new StreamSource(inputStream), stepOneResult);
		
		// Step Two: Preprocess the result of the previous stage to convert the abstract patterns into real patterns
		stepTwoXSLT = new StreamSource(new File(STEP_TWO_FILENAME));
		stepTwo = mTransformerFactory.newTransformer(stepTwoXSLT);
		stepTwo.setErrorListener(mReporter);
		stepTwoResult = new DOMResult();
		stepTwo.transform(new DOMSource(stepOneResult.getNode()), stepTwoResult);
			
		// Step Three: Compile the result of the previous stage into a XSLT script.
		StreamSource schXSLT = new StreamSource(new File(STEP_THREE_FILENAME));
		mSchematronTransformer = mTransformerFactory.newTransformer(schXSLT);
		mSchematronTransformer.setErrorListener(mReporter);
		mTransformResult = new DOMResult();
		mSchematronTransformer.transform(new DOMSource(stepTwoResult.getNode()), mTransformResult);

		
		// Step Four(A): Prepare the transformer to run the script from 
		// the previous stage against the XML document to validate
		Source source = new DOMSource(((DOMResult) mTransformResult).getNode());
		mXMLTransformer = mTransformerFactory.newTransformer(source);
		mXMLTransformer.setErrorListener(mReporter);
	
//		// The validator is brand new, so it's up to date.
		mUpdate = false;
	}
	
	/**
	 * Validate the input XML against this SchematronValidator
	 * @param xml The XML document to validate as an AbstractXMLModel.
	 * @throws TransformerException 
	 */
	public void validate(AbstractXMLModel xml) throws TransformerException {
		
		// If the intermediate XSTL has not been created, we've got a problem
		if(mXMLTransformer==null) {
			// TODO
			System.err.println("Error en SchematronValidator!!! Transformador intermedio no ha sido inicializado.");
		}
		
		// Use the final transformer to transform the XML document
		DOMResult result = new DOMResult();
//		Result result = new StreamResult(System.out);
		Source source = new StreamSource(xml.getTextAsStream());
		
		// Step Four (B): apply the XSLT script generated from Step Three against the XML document
		mXMLTransformer.transform(source, result);
		mReporter.setSVRLReport(new SvrlDomModel(result.getNode()));
		mReporter.printSvrlFailedMessages();

	}
	
	/**
	 * Obtains if the validator is outdated and needs to be rebuilt
	 * @return true if it's outdated, false otherwise
	 */
	public boolean needsUpdate()
	{
		return mUpdate;
	}
	
	/**
	 * Marks the validator as outdated, so it will be rebuilt
	 * next time it's used.
	 */
	public void markForUpdate() {
		mUpdate = true;
	}
	
	/**
	 * Sets the the reporter that will handle validating errors
	 * @param reporter ErrorReporter that will handle validating errors
	 */
	public void setErrorReporter(SchematronErrorReporter reporter) {
		mReporter = reporter;
	}
}
